﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Data;
using System.Globalization;
using System.Windows.Automation;

namespace MockupDesigner.AdditionalControls
{
    [TemplatePart(Name = NumericUpDown.PART_EditableTextBox, Type = typeof(TextBox))]
    public class NumericUpDown : Control
    {
        internal const string PART_EditableTextBox = "PART_EditableTextBox";
        static NumericUpDown()
        {
            InitializeCommands();

            // Listen to MouseLeftButtonDown event to determine if slide should move focus to itself
            EventManager.RegisterClassHandler(typeof(NumericUpDown),
                Mouse.MouseDownEvent, new MouseButtonEventHandler(NumericUpDown.OnMouseLeftButtonDown), true);

            DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
        }
        //</SnippetStaticCtorOfCustomClassCommonTasks>

        public NumericUpDown()
            : base()
        {
            updateValueString();
        }

        #region Properties
        #region Value
        public decimal Value
        {
            get { return (decimal)GetValue(ValueProperty); }
            set {
                SetValue(ValueProperty, value); 
            }
        }

        /// <summary>
        /// Identifies the Value dependency property.
        /// </summary>
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(
                "Value", typeof(decimal), typeof(NumericUpDown),
                new FrameworkPropertyMetadata(DefaultValue,
                    new PropertyChangedCallback(OnValueChanged),
                    new CoerceValueCallback(CoerceValue)
                )
            );

        private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown control = (NumericUpDown)obj;

            decimal oldValue = (decimal)args.OldValue;
            decimal newValue = (decimal)args.NewValue;

            #region Fire Automation events
            NumericUpDownAutomationPeer peer = UIElementAutomationPeer.FromElement(control) as NumericUpDownAutomationPeer;
            if (peer != null)
            {
                peer.RaiseValueChangedEvent(oldValue, newValue);
            }
            #endregion

            RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
                oldValue, newValue, ValueChangedEvent);

            control.OnValueChanged(e);

            control.updateValueString();
        }

        /// <summary>
        /// Raises the ValueChanged event.
        /// </summary>
        /// <param name="args">Arguments associated with the ValueChanged event.</param>
        protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
        {
            RaiseEvent(args);
        }

        private static object CoerceValue(DependencyObject element, object value)
        {
            decimal newValue = (decimal)value;
            NumericUpDown control = (NumericUpDown)element;
            newValue = control.CoerceValue(newValue);
            return newValue;
        }
        /// <summary>Enforces <see cref="Minimum"/>, <see cref="Maximum"/> and <see cref="DecimalPlaces"/> on given value</summary>
        /// <param name="Value">Value to be coerced</param>
        /// <returns>Value derived from <paramref name="Value"/> that fullfills the constrains</returns>
        protected virtual decimal CoerceValue(decimal Value)
        {
            Value = Math.Max(this.Minimum, Math.Min(this.Maximum, Value));
            Value = Decimal.Round(Value, this.DecimalPlaces);
            return Value;
        }
        #endregion
        #region Minimum
        public decimal Minimum
        {
            get { return (decimal)GetValue(MinimumProperty); }
            set { SetValue(MinimumProperty, value); }
        }

        public static readonly DependencyProperty MinimumProperty =
            DependencyProperty.Register(
                "Minimum", typeof(decimal), typeof(NumericUpDown),
                new FrameworkPropertyMetadata(DefaultMinValue,
                    new PropertyChangedCallback(OnMinimumChanged), new CoerceValueCallback(CoerceMinimum)
                )
            );

        private static void OnMinimumChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            element.CoerceValue(MaximumProperty);
            element.CoerceValue(ValueProperty);
        }
        private static object CoerceMinimum(DependencyObject element, object value)
        {
            decimal minimum = (decimal)value;
            NumericUpDown control = (NumericUpDown)element;
            return Decimal.Round(minimum, control.DecimalPlaces);
        }
        #endregion
        #region Maximum
        public decimal Maximum
        {
            get { return (decimal)GetValue(MaximumProperty); }
            set { SetValue(MaximumProperty, value); }
        }

        public static readonly DependencyProperty MaximumProperty =
            DependencyProperty.Register(
                "Maximum", typeof(decimal), typeof(NumericUpDown),
                new FrameworkPropertyMetadata(DefaultMaxValue,
                    new PropertyChangedCallback(OnMaximumChanged),
                    new CoerceValueCallback(CoerceMaximum)
                )
            );

        private static void OnMaximumChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            element.CoerceValue(ValueProperty);
        }

        private static object CoerceMaximum(DependencyObject element, object value)
        {
            NumericUpDown control = (NumericUpDown)element;
            decimal newMaximum = (decimal)value;
            return Decimal.Round(Math.Max(newMaximum, control.Minimum), control.DecimalPlaces);
        }
        #endregion
        #region Change
        public decimal Change
        {
            get { return (decimal)GetValue(ChangeProperty); }
            set { SetValue(ChangeProperty, value); }
        }

        public static readonly DependencyProperty ChangeProperty =
            DependencyProperty.Register(
                "Change", typeof(decimal), typeof(NumericUpDown),
                new FrameworkPropertyMetadata(DefaultChange, new PropertyChangedCallback(OnChangeChanged), new CoerceValueCallback(CoerceChange)),
            new ValidateValueCallback(ValidateChange)
            );

        private static bool ValidateChange(object value)
        {
            decimal change = (decimal)value;
            return change > 0;
        }

        private static void OnChangeChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {

        }

        private static object CoerceChange(DependencyObject element, object value)
        {
            decimal newChange = (decimal)value;
            NumericUpDown control = (NumericUpDown)element;

            decimal coercedNewChange = Decimal.Round(newChange, control.DecimalPlaces);

            //If Change is .1 and DecimalPlaces is changed from 1 to 0, we want Change to go to 1, not 0.
            //Put another way, Change should always be rounded to DecimalPlaces, but never smaller than the 
            //previous Change
            if (coercedNewChange < newChange)
            {
                coercedNewChange = smallestForDecimalPlaces(control.DecimalPlaces);
            }

            return coercedNewChange;
        }

        private static decimal smallestForDecimalPlaces(int decimalPlaces)
        {
            if (decimalPlaces < 0)
            {
                throw new ArgumentException("decimalPlaces");
            }

            decimal d = 1;

            for (int i = 0; i < decimalPlaces; i++)
            {
                d /= 10;
            }

            return d;
        }

        #endregion
        #region DecimalPlaces
        public int DecimalPlaces
        {
            get { return (int)GetValue(DecimalPlacesProperty); }
            set { SetValue(DecimalPlacesProperty, value); }
        }

        public static readonly DependencyProperty DecimalPlacesProperty =
            DependencyProperty.Register(
                "DecimalPlaces", typeof(int), typeof(NumericUpDown),
                new FrameworkPropertyMetadata(DefaultDecimalPlaces,
                    new PropertyChangedCallback(OnDecimalPlacesChanged)
                ), new ValidateValueCallback(ValidateDecimalPlaces)
            );

        private static void OnDecimalPlacesChanged(DependencyObject element, DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown control = (NumericUpDown)element;
            control.CoerceValue(ChangeProperty);
            control.CoerceValue(MinimumProperty);
            control.CoerceValue(MaximumProperty);
            control.CoerceValue(ValueProperty);
            control.updateValueString();
        }

        private static bool ValidateDecimalPlaces(object value)
        {
            int decimalPlaces = (int)value;
            return decimalPlaces >= 0;
        }

        #endregion

        #region ValueString
        public string ValueString
        {
            get { return (string)GetValue(ValueStringProperty); }
            set { SetValue(ValueStringProperty, value); }
        }

        public static readonly DependencyProperty ValueStringProperty =
            DependencyProperty.RegisterAttached("ValueString", typeof(string), typeof(NumericUpDown), new PropertyMetadata(
                    DefaultValue.ToString(CultureInfo.InvariantCulture),
                    new PropertyChangedCallback(OnValueStringChanged),
                    new CoerceValueCallback(CoerceValueString)
                ), new ValidateValueCallback(ValidateValueString));

        //public static readonly DependencyProperty ValueStringProperty = ValueStringPropertyKey.DependencyProperty;

        private void updateValueString()
        {
            string newValueString = ValueToString(this.Value);
            this.SetValue(ValueStringProperty, newValueString);
        }
        protected virtual string ValueToString(decimal value)
        {
            return value.ToString(string.Format("f{0}", this.DecimalPlaces, CultureInfo.InvariantCulture));
        }

        private static void OnValueStringChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown control = (NumericUpDown)obj;

            string oldValue = (string)args.OldValue;
            string newValue = (string)args.NewValue;

            //#region Fire Automation events
            //NumericUpDownAutomationPeer peer = UIElementAutomationPeer.FromElement(control) as NumericUpDownAutomationPeer;
            //if(peer != null) {
            //    peer.RaiseValueStringChangedEvent(oldValue, newValue);
            //}
            //#endregion

            RoutedPropertyChangedEventArgs<string> e = new RoutedPropertyChangedEventArgs<string>(
                oldValue, newValue, ValueStringChangedEvent);

            control.OnValueStringChanged(e);
            control.SetValue(ValueProperty, control.TextToValue(newValue, control.Value));
        }

        /// <summary>
        /// Raises the ValueChanged event.
        /// </summary>
        /// <param name="args">Arguments associated with the ValueChanged event.</param>
        protected virtual void OnValueStringChanged(RoutedPropertyChangedEventArgs<string> args)
        {
            RaiseEvent(args);
        }

        private static object CoerceValueString(DependencyObject element, object value)
        {
            string newStrValue = (string)value;
            NumericUpDown control = (NumericUpDown)element;
            decimal newDecimal = control.TextToValue(newStrValue, control.Value);
            newDecimal = control.CoerceValue(newDecimal);
            string newStr = control.ValueToString(newDecimal);
            return newStr;
        }
        private static bool ValidateValueString(object value)
        {
            string str = value as string;
            if (str == null) return false;
            decimal newValue;
            bool CanParse = decimal.TryParse(str, out newValue);
            return CanParse;
        }
        #endregion

        public bool IsEditable
        {
            get { return (bool)GetValue(IsEditableProperty); }
            set { SetValue(IsEditableProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsEditable.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsEditableProperty =
            DependencyProperty.Register("IsEditable", typeof(bool), typeof(NumericUpDown), new UIPropertyMetadata(true));


        #endregion

        #region Events
        /// <summary>
        /// Identifies the ValueChanged routed event.
        /// </summary>
        public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
            "ValueChanged", RoutingStrategy.Bubble,
            typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

        /// <summary>
        /// Identifies the ValueStringChanged routed event.
        /// </summary>
        public static readonly RoutedEvent ValueStringChangedEvent = EventManager.RegisterRoutedEvent(
            "ValueStringChanged", RoutingStrategy.Bubble,
            typeof(RoutedPropertyChangedEventHandler<string>), typeof(NumericUpDown));

        /// <summary>
        /// Occurs when the Value property changes.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }
        /// <summary>
        /// Occurs when the ValueString property changes.
        /// </summary>
        public event RoutedPropertyChangedEventHandler<string> ValueStringChanged
        {
            add { AddHandler(ValueStringChangedEvent, value); }
            remove { RemoveHandler(ValueStringChangedEvent, value); }
        }
        #endregion

        #region Commands

        public static RoutedCommand IncreaseCommand
        {
            get
            {
                return _increaseCommand;
            }
        }
        public static RoutedCommand DecreaseCommand
        {
            get
            {
                return _decreaseCommand;
            }
        }

        private static void InitializeCommands()
        {
            _increaseCommand = new RoutedCommand("IncreaseCommand", typeof(NumericUpDown));
            CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown), new CommandBinding(_increaseCommand, OnIncreaseCommand));
            CommandManager.RegisterClassInputBinding(typeof(NumericUpDown), new InputBinding(_increaseCommand, new KeyGesture(Key.Up)));

            _decreaseCommand = new RoutedCommand("DecreaseCommand", typeof(NumericUpDown));
            CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown), new CommandBinding(_decreaseCommand, OnDecreaseCommand));
            CommandManager.RegisterClassInputBinding(typeof(NumericUpDown), new InputBinding(_decreaseCommand, new KeyGesture(Key.Down)));
        }

        private static void OnIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
        {
            NumericUpDown control = sender as NumericUpDown;
            if (control != null)
            {
                control.OnIncrease();
            }
        }
        private static void OnDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
        {
            NumericUpDown control = sender as NumericUpDown;
            if (control != null)
            {
                control.OnDecrease();
            }
        }

        protected virtual void OnIncrease()
        {
            this.Value += Change;
        }
        protected virtual void OnDecrease()
        {
            this.Value -= Change;
        }

        private static RoutedCommand _increaseCommand;
        private static RoutedCommand _decreaseCommand;
        #endregion

        #region Automation
        //<SnippetOnCreateAutomationPeer>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new NumericUpDownAutomationPeer(this);
        }
        //</SnippetOnCreateAutomationPeer>
        #endregion

        /// <summary>
        /// This is a class handler for MouseLeftButtonDown event.
        /// The purpose of this handle is to move input focus to NumericUpDown when user pressed
        /// mouse left button on any part of slider that is not focusable.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            NumericUpDown control = (NumericUpDown)sender;

            // When someone click on a part in the NumericUpDown and it's not focusable
            // NumericUpDown needs to take the focus in order to process keyboard correctly
            if (!control.IsKeyboardFocusWithin)
            {
                e.Handled = control.Focus() || e.Handled;
            }
        }

        private const decimal DefaultMinValue = 0,
            DefaultValue = DefaultMinValue,
            DefaultMaxValue = 100,
            DefaultChange = 1;
        private const int DefaultDecimalPlaces = 0;

        #region TextBox Validation
        /// <summary>Current <see cref="TextBox"/> (if any)</summary>
        private TextBox textBox;
        /// <summary>Invoked whenever application code or internal processes call <see cref="System.Windows.FrameworkElement.ApplyTemplate"/>.</summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            var TemplateObj = this.Template.FindName(PART_EditableTextBox, this);
            DetachTextBoxEvents();
            if (TemplateObj is TextBox)
            {
                textBox = (TextBox)TemplateObj;
                AttachTextBoxEvents();
            }
            else
                textBox = null;
        }
        /// <summary>Hooks events of <see cref="textBox"/></summary>
        private void AttachTextBoxEvents()
        {
            if (textBox == null) return;
            textBox.PreviewTextInput += new TextCompositionEventHandler(textBox_PreviewTextInput);
            textBox.LostFocus += new RoutedEventHandler(textBox_LostFocus);
        }
        /// <summary>Unhooks events of <see cref="textBox"/></summary>
        private void DetachTextBoxEvents()
        {
            if (textBox == null) return;
            textBox.PreviewTextInput += new TextCompositionEventHandler(textBox_PreviewTextInput);
            textBox.LostFocus -= new RoutedEventHandler(textBox_LostFocus);
        }
        /// <summary>Prevents non-number characters from being typed</summary>
        private void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            if (this.IsEditable)
                e.Handled = !AreCharsAccepltable(e.Text);
        }
        private void textBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (this.IsEditable)
            {
                this.Value = TextToValue(textBox.Text, this.Value);
                this.ValueString = ValueToString(this.Value);
                textBox.Text = this.ValueString;
            }
        }
        /// <summary>Attempts to convert user-typed text to number</summary>
        /// <param name="text">Text to converts</param>
        /// <param name="fallback">Value to be retuned if <paramref name="text"/> cannot be converted to decimal</param>
        /// <returns>
        /// <paramref name="text"/> converted to number. It conversion is not possible returns <paramref name="fallback"/>.
        /// Return value should be acceptable by <see cref="Minimum"/>, <see cref="Maximum"/> and <see cref="DecimalPlaces"/> constraints.
        /// <para>This implementation utilizes <see cref="CoerceValue(decimal)"/></para>
        /// </returns>
        protected virtual decimal TextToValue(string text, decimal fallback)
        {
            decimal newValue;
            if (decimal.TryParse(text, out newValue))
            {
                return (decimal)CoerceValue(this, newValue);
            }
            else
            {
                return fallback;
            }
        }
        /// <summary>For given string gets value indicating if it consists only of characters acceptable as part of number</summary>
        /// <param name="text">String to verify</param>
        /// <returns>True if <paramref name="text"/> consists only of characters valid as part of number. Takes current culture and current constrainst in account.</returns>
        /// <remarks>Returns true even for string which consists for valid character but is invalid number (i.e. "4.-..5" in invariant culture)</remarks>
        protected virtual bool AreCharsAccepltable(string text)
        {
            if (string.IsNullOrEmpty(text)) return true;
            var NumberFormat = CultureInfo.CurrentCulture.NumberFormat;
            foreach (char ch in text)
            {
                if (
                    (NumberFormat.NumberDecimalSeparator.Contains(new string(ch, 1)) && this.DecimalPlaces > 0) ||
                    (NumberFormat.NumberGroupSeparator.Contains(new string(ch, 1))) ||
                    (char.IsDigit(ch)) ||
                    (NumberFormat.NegativeSign.Contains(new string(ch, 1)) && this.Minimum < 0) ||
                    (NumberFormat.PositiveSign.Contains(new string(ch, 1)) && this.Maximum > 0)
                ) continue;
                return false;
            }
            return true;
        }
        #endregion
    }
    public class NumericUpDownAutomationPeer : FrameworkElementAutomationPeer, IRangeValueProvider
    {
        public NumericUpDownAutomationPeer(NumericUpDown control)
            : base(control)
        {
        }

        protected override string GetClassNameCore()
        {
            return "NumericUpDown";
        }

        protected override AutomationControlType GetAutomationControlTypeCore()
        {
            return AutomationControlType.Spinner;
        }


        //<SnippetGetPattern>
        public override object GetPattern(PatternInterface patternInterface)
        {
            if (patternInterface == PatternInterface.RangeValue)
            {
                return this;
            }
            return base.GetPattern(patternInterface);
        }
        //</SnippetGetPattern>

        internal void RaiseValueChangedEvent(decimal oldValue, decimal newValue)
        {
            base.RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty,
                (double)oldValue, (double)newValue);
        }
        //internal void RaiseValueStringChangedEvent(string oldValue, string newValue) {
        //    base.RaisePropertyChangedEvent(NumericUpDown.ValueStringProperty,
        //        (string)oldValue, (string)newValue);
        //}

        #region IRangeValueProvider Members

        bool IRangeValueProvider.IsReadOnly
        {
            get
            {
                return !IsEnabled();
            }
        }

        double IRangeValueProvider.LargeChange
        {
            get { return (double)MyOwner.Change; }
        }

        double IRangeValueProvider.Maximum
        {
            get { return (double)MyOwner.Maximum; }
        }

        double IRangeValueProvider.Minimum
        {
            get { return (double)MyOwner.Minimum; }
        }

        void IRangeValueProvider.SetValue(double value)
        {
            if (!IsEnabled())
            {
                throw new ElementNotEnabledException();
            }

            decimal val = (decimal)value;
            if (val < MyOwner.Minimum || val > MyOwner.Maximum)
            {
                throw new ArgumentOutOfRangeException("value");
            }

            MyOwner.Value = val;
        }

        double IRangeValueProvider.SmallChange
        {
            get { return (double)MyOwner.Change; }
        }

        double IRangeValueProvider.Value
        {
            get { return (double)MyOwner.Value; }
        }

        #endregion

        private NumericUpDown MyOwner
        {
            get
            {
                return (NumericUpDown)base.Owner;
            }
        }
        //<SnippetClose>
    }
    //</SnippetClose>


    internal class NotBooleanValueConverter : IValueConverter
    {

        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool && targetType.Equals(typeof(bool))) return !(bool)value;
            throw new ArgumentException("Unsupported conversion");
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool && targetType.Equals(typeof(bool))) return !(bool)value;
            throw new ArgumentException("Unsupported conversion");
        }

        #endregion
    }
    internal class HalfConverter : IValueConverter
    {

        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Convert(value, targetType, (Single)0.5);

        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Convert(value, targetType, (Single)2);
        }

        #endregion
        private static object Convert(object value, Type targetType, Single param)
        {
            double multiplied;
            if (value is sbyte) multiplied = (double)((double)(sbyte)value * param);
            else if (value is byte) multiplied = (double)((double)(byte)value * param);
            else if (value is short) multiplied = (double)((double)(short)value * param);
            else if (value is ushort) multiplied = (double)((double)(ushort)value * param);
            else if (value is int) multiplied = (double)((double)(int)value * param);
            else if (value is uint) multiplied = (double)((double)(uint)value * param);
            else if (value is long) multiplied = (double)((double)(long)value * param);
            else if (value is ulong) multiplied = (double)((double)(ulong)value * param);
            else if (value is System.Single) multiplied = (double)((double)(System.Single)value * param);
            else if (value is double) multiplied = (double)((double)(double)value * param);
            else if (value is decimal) multiplied = (double)((double)(decimal)value * param);
            else throw new ArgumentException("Unsupported source type", "value");
            if (targetType.Equals(typeof(sbyte))) return (sbyte)multiplied;
            else if (targetType.Equals(typeof(byte))) return (byte)multiplied;
            else if (targetType.Equals(typeof(short))) return (short)multiplied;
            else if (targetType.Equals(typeof(ushort))) return (ushort)multiplied;
            else if (targetType.Equals(typeof(int))) return (int)multiplied;
            else if (targetType.Equals(typeof(uint))) return (uint)multiplied;
            else if (targetType.Equals(typeof(long))) return (long)multiplied;
            else if (targetType.Equals(typeof(ulong))) return (ulong)multiplied;
            else if (targetType.Equals(typeof(Single))) return (Single)multiplied;
            else if (targetType.Equals(typeof(double))) return (double)multiplied;
            else if (targetType.Equals(typeof(decimal))) return (decimal)multiplied;
            else throw new ArgumentException("Unsupported target type", "targetType");
        }
    }
}
